package com.gxuwz.annotation;

import java.lang.reflect.Method;  
import java.util.List;  
  
import org.apache.log4j.Logger;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.reflect.MethodSignature;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;   
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.expression.ExpressionParser;  
import org.springframework.expression.spel.standard.SpelExpressionParser;  
import org.springframework.expression.spel.support.StandardEvaluationContext;  
import org.springframework.stereotype.Component;  
  
import com.alibaba.fastjson.JSON;  
/** 
 * redis切面处理 
* @ClassName: CacheAspect  
* @author caixl  
* @date 2016-6-14 上午10:14:08  
* 
 */  
@Component  
@Aspect  
public class RedisCacheAspect {  
  
    public static final Logger infoLog = Logger.getLogger(RedisCacheAspect.class);  
  
    @Autowired  
    private RedisTemplate redisTemplate;
    
    /**
     * 分隔符 生成key 格式为 类全类名|方法名|参数所属类全类名
     **/
    private static final String DELIMITER = "|";
  
    /** 
     * 获取或添加缓存 
     * @param jp 
     * @param cache 
     * @return 
     * @throws Throwable 
     */  
    @SuppressWarnings("unchecked")  
    @Around("@annotation(com.gxuwz.annotation.RedisCache)")  
    public Object RedisCache(final ProceedingJoinPoint jp) throws Throwable {  
        Method method = getMethod(jp);  
        RedisCache cache = method.getAnnotation(RedisCache.class);
        
        // 得到类名、方法名和参数
        String clazzName = jp.getTarget().getClass().getName();
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();  
          
          
        // 根据类名，方法名和参数生成key  
        final String key = getKey(clazzName, methodName, args); 
        if (infoLog.isDebugEnabled()) {  
            infoLog.debug("生成key:" + key);  
        }  
  
        // 得到被代理的方法  
        //Method me = ((MethodSignature) jp.getSignature()).getMethod();  
        // 得到被代理的方法上的注解  
        Class modelType = method.getAnnotation(RedisCache.class).type();  
  
        // 检查redis中是否有缓存  
        String value = (String) redisTemplate.opsForHash().get(modelType.getName(), key);  
  
        // result是方法的最终返回结果  
        Object result = null;  
        if (null == value) {  
            // 缓存未命中  
            if (infoLog.isDebugEnabled()) {  
                infoLog.debug("缓存未命中");  
            }  
  
            // 调用数据库查询方法  
            result = jp.proceed(args);  
  
            // 序列化查询结果  
            final String  json = serialize(result);  
            final String hashName = modelType.getName();  
            final int expire = cache.expire();   
            // 序列化结果放入缓存  
            redisTemplate.opsForHash().put(modelType.getName(), key, json);  
        } else {  
            // 缓存命中  
            if (infoLog.isDebugEnabled()) {  
                infoLog.debug("缓存命中, value = " + value);  
            }  
  
            // 得到被代理方法的返回值类型  
            Class returnType = ((MethodSignature) jp.getSignature())  
                    .getReturnType();  
  
            // 反序列化从缓存中拿到的json  
            result = deserialize(value, returnType, modelType);  
  
            if (infoLog.isDebugEnabled()) {  
                infoLog.debug("反序列化结果 = {}" + result);  
            }  
        }  
  
        return result;  
    }  
    /** 
     * 删除缓存 
     * @param jp 
     * @param cache 
     * @return 
     * @throws Throwable 
     */  
    @Around("@annotation(com.gxuwz.annotation.RedisEvict)")  
    public Object RedisEvict(final ProceedingJoinPoint jp)  throws Throwable {  
        // 得到被代理的方法  
        Method me = ((MethodSignature) jp.getSignature()).getMethod();  
        // 得到被代理的方法上的注解  
        Class modelType = me.getAnnotation(RedisEvict.class).type();  
  
        if (infoLog.isDebugEnabled()) {  
            infoLog.debug("清空缓存:" + modelType.getName());  
        }  
        
        // 清除对应缓存   
        redisTemplate.delete(modelType.getName());  
  
        return jp.proceed(jp.getArgs());  
    }  
     /** 
     *  获取被拦截方法对象 
     *   
     *  MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 
     *  而缓存的注解在实现类的方法上 
     *  所以应该使用反射获取当前对象的方法对象 
     */  
    public Method getMethod(ProceedingJoinPoint pjp){  
      //获取参数的类型  
      Object [] args=pjp.getArgs();  
      Class [] argTypes=new Class[pjp.getArgs().length];  
      for(int i=0;i<args.length;i++){  
        argTypes[i]=args[i].getClass();  
      }  
      Method method=null;  
      try {  
        method=pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(),argTypes);  
      } catch (NoSuchMethodException e) {  
        e.printStackTrace();  
      } catch (SecurityException e) {  
        e.printStackTrace();  
      }  
      return method;  
        
    }  
 
    /** 
     * 序列化 
     * @param target 
     * @return 
     */  
    protected String serialize(Object target) {  
        return JSON.toJSONString(target);  
    }  
    /** 
     * 反序列化 
     * @param jsonString 
     * @param clazz 
     * @param modelType 
     * @return 
     */  
    protected Object deserialize(String jsonString, Class clazz, Class modelType) {  
        // 序列化结果应该是List对象  
        if (clazz.isAssignableFrom(List.class)) {  
            return JSON.parseArray(jsonString, modelType);  
        }  
  
        // 序列化结果是普通对象  
        return JSON.parseObject(jsonString, clazz);  
    } 
    
    /**
     *      * 根据类名、方法名和参数生成Key
     *      * @param clazzName
     *      * @param methodName
     *      * @param args
     *      * @return key格式：全类名|方法名｜参数类型
     *      
     */
    private String getKey(String clazzName, String methodName, Object[] args) {
        StringBuilder key = new StringBuilder(clazzName);
        key.append(DELIMITER);
        key.append(methodName);
        key.append(DELIMITER);

        for (Object obj : args) {
            key.append(obj.getClass().getSimpleName());
            key.append(DELIMITER);
        }

        return key.toString();
    }
    
}  